{-
Copyright 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-}

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

-- | Defines a type class for polyhedra that ties together its faces, edges, and
-- vertices.
module Twisty.Polyhedron where

import Data.Array.IArray ((!), Array, Ix, array, listArray)
import Data.Bits ((.|.), bit, shiftL)
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.List (inits, tails)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromJust, maybeToList)


-- | A class for the faces of polyhedra used as the bases for twisty puzzles;
-- contains associated types for the edges and vertices.
class (Enum f, Bounded f, Ix f,
       Enum (PolyEdge f), Bounded (PolyEdge f),
       Enum (PolyVertex f), Bounded (PolyVertex f))
      => PolyFace f where
  data PolyEdge f
  data PolyVertex f

  faceNames :: [(f, Char)]
  -- ^ Every face of the polyhedron must be identified by a single character,
  -- and listed here.

  neighboringFaces :: f -> [f]
  -- ^ The faces that touch a given face, in clockwise order.

  allEdgesAsFaces :: [[f]]
  -- ^ All the edges, as length-2 lists of faces with the distinguished face
  -- first.

  allVerticesAsFaces :: [[f]]
  -- ^ All the vertices, as lists of their faces.  The faces must appear in
  -- clockwise order, starting with the vertex's distinguished face.


-- | A face's single-character name.
faceToName :: (PolyFace f) => f -> Char
faceToName = (nameArray !)
  where nameArray :: (PolyFace f) => Array f Char
        nameArray = array (minBound, maxBound) faceNames

-- | Converts back from a face's name to the face.
nameToFace :: (PolyFace f) => Char -> f
nameToFace = fromJust . nameToMaybeFace

-- | Converts back from a face's name to the face, if it is one.
nameToMaybeFace :: (PolyFace f) => Char -> Maybe f
nameToMaybeFace = flip Map.lookup faceMap
  where faceMap :: (PolyFace f) => Map Char f
        faceMap = Map.fromList $ map (\(f, c) -> (c, f)) faceNames

-- | Converts a list of faces into an int, as a bit set.
facesIndex :: forall f. (PolyFace f) => [f] -> Int
facesIndex [] = 0
facesIndex (f:fs) = toBit f .|. facesIndex fs
  where toBit f = bit $ fromEnum f - fromEnum (minBound::f)

-- | The neighbor just counterclockwise of the given face's given neighbor.
previousNeighbor :: (PolyFace f) => f -> f -> f
previousNeighbor f n = if n == head ns then last ns else prev ns
  where ns = neighboringFaces f
        prev (f:fs@(f2:_)) = if n == f2 then f else prev fs

-- | The neighbor just clockwise of the given face's given neighbor.
nextNeighbor :: (PolyFace f, Show f) => f -> f -> f
nextNeighbor f n = next ns
  where ns = neighboringFaces f
        next (f:fs@(f2:_)) = if n == f then f2 else next fs
        next [f] = if n == f then head ns
                   else error (show n ++ " isn't a neighbor of " ++ show origF)
        origF = f

-- | Tells whether two faces are neighbors.
neighbors :: (PolyFace f) => f -> f -> Bool
neighbors f = flip elem $ neighboringFaces f

-- | The edges of a given face, as length-2 lists of their faces.  The given
-- face appears first.
faceEdgesAsFaces :: (PolyFace f) => f -> [[f]]
faceEdgesAsFaces f = map (\f2 -> [f, f2]) $ neighboringFaces f

-- | The canonical name of an edge.
edgeName :: (PolyFace f) => PolyEdge f -> String
edgeName = map faceToName . edgeFaces

-- | The 2 faces of an edge, in canonical order.
edgeFaces :: (PolyFace f) => PolyEdge f -> [f]
edgeFaces = (facesArray !) . fromEnum
  where facesArray = makeFacesArray allEdgesAsFaces

-- | Converts a string containing 2 face names into the
-- corresponding edge.
nameToEdge :: (PolyFace f) => String -> PolyEdge f
nameToEdge = facesToEdge . map nameToFace

-- | Converts a pair of faces into the corresponding edge.
facesToEdge :: (PolyFace f) => [f] -> PolyEdge f
facesToEdge = fromJust . facesToMaybeEdge

-- | Converts a pair of faces into the corresponding edge, if there is one.
facesToMaybeEdge :: (PolyFace f) => [f] -> Maybe (PolyEdge f)
facesToMaybeEdge = flip IntMap.lookup edgeMap . facesIndex
  where edgeMap = makeFaceBitsetMap edgeFaces

-- | The edges that belong to a given face, in clockwise order.
faceEdges :: (PolyFace f) => f -> [PolyEdge f]
faceEdges = (edgesArray !)
  where edgesArray :: (PolyFace f) => Array f [PolyEdge f]
        edgesArray = listArray (minBound, maxBound)
                     [es f | f <- [minBound..]]
        es :: (PolyFace f) => f -> [PolyEdge f]
        es = map facesToEdge . efs
        efs :: (PolyFace f) => f -> [[f]]
        efs f = map (\f2 -> [f, f2]) (neighboringFaces f)


-- | The vertices of a given face, as lists of the faces that meet at each
-- vertex.  The faces for each vertex appear in clockwise order starting with
-- the given face.  And the vertices corresponding to the lists of faces are
-- also in clockwise order.
faceVerticesAsFaces :: forall f. (PolyFace f) => f -> [[f]]
faceVerticesAsFaces f = map vfs $ neighboringFaces f
  where vfs :: f -> [f]
        vfs f2 = (Map.!) facesMap (f, f2)
        facesMap :: Map (f, f) [f]
        facesMap = Map.fromList $ concatMap fvs allVerticesAsFaces
        fvs :: [f] -> [((f, f), [f])]
        fvs = map indexFaces . allRotations
        allRotations :: [f] -> [[f]]
        allRotations fs = map (uncurry (++)) $ tail $ zip (tails fs) (inits fs)
        indexFaces :: [f] -> ((f, f), [f])
        indexFaces fs@(f1:f2:_) = ((f1, f2), fs)

-- | The canonical name of a vertex.
vertexName :: (PolyFace f) => PolyVertex f -> String
vertexName = map faceToName . vertexFaces

-- | The faces of a vertex, in canonical order.
vertexFaces :: (PolyFace f) => PolyVertex f -> [f]
vertexFaces = (facesArray !) . fromEnum
  where facesArray = makeFacesArray allVerticesAsFaces

-- | Converts a string containing face names into the corresponding vertex.
nameToVertex :: (PolyFace f) => String -> PolyVertex f
nameToVertex = facesToVertex . map nameToFace

-- | Converts a list of faces into the corresponding vertex.
facesToVertex :: (PolyFace f) => [f] -> PolyVertex f
facesToVertex = fromJust . facesToMaybeVertex

-- | Converts a list of faces into the corresponding vertex, if there is one.
facesToMaybeVertex :: (PolyFace f) => [f] -> Maybe (PolyVertex f)
facesToMaybeVertex = flip IntMap.lookup vertexMap . facesIndex
  where vertexMap = makeFaceBitsetMap vertexFaces

-- | The vertices that belong to a given face, in clockwise order.
faceVertices :: forall f. (PolyFace f) => f -> [PolyVertex f]
faceVertices = (verticesArray !)
  where verticesArray :: Array f [PolyVertex f]
        verticesArray = listArray (minBound, maxBound)
                        [vs f | f <- [minBound..]]
        vs :: f -> [PolyVertex f]
        vs = map facesToVertex . faceVerticesAsFaces


-- | A helper to implement edgeFaces and vertexFaces.  Makes an array from a
-- list of lists of faces, such as allVerticesAsFaces and allEdgesAsFaces.
makeFacesArray :: [[f]] -> Array Int [f]
makeFacesArray fss = listArray (0, length fss - 1) fss

-- | A helper to implement facesToMaybeEdge and facesToMaybeVertex.  Makes a map
-- that maps a bit-set of face numbers to either edges or vertices.
makeFaceBitsetMap :: (Enum a, Bounded a, PolyFace f) =>
                     (a -> [f]) -> IntMap a
makeFaceBitsetMap xxFaces =
  IntMap.fromList [(i, a) | a <- [minBound..], let i = facesIndex $ xxFaces a]

-- | A helper to implement the toEnum methods of edge and vertex types.
toBoundedEnum :: forall a. (Bounded a, Enum a) => (Int -> a) -> Int -> a
toBoundedEnum ctor i = if i < fromEnum (minBound::a) || i > fromEnum (maxBound::a)
                       then error "Enum out of bounds"
                       else ctor i

-- | A helper to implement allVerticesAsFaces for polyhedra with 3 faces per
-- vertex.
faceNeighborTriples :: (PolyFace f) => f -> [[f]]
faceNeighborTriples f = vt ns $ tail $ cycle ns
  where ns = neighboringFaces f
        vt [] _ = []
        vt (x:xs) (y:ys) = [f, x, y] : vt xs ys


-- | A helper to implement Read for face types.
readSFace :: (PolyFace f) => ReadS f
readSFace (c:cs) = maybeToList $ do
  f <- nameToMaybeFace c
  return (f, cs)
readSFace _ = []

-- | A helper to implement Read for edge types.
readSEdge :: (PolyFace f) => ReadS (PolyEdge f)
readSEdge (c1:c2:cs) = maybeToList $ do
  f1 <- nameToMaybeFace c1
  f2 <- nameToMaybeFace c2
  e <- facesToMaybeEdge [f1, f2]
  return (e, cs)
readSEdge _ = []

-- | A helper to implement Read for vertex types (for polyhedra whose vertices
-- have 3 faces).
readSVertex :: (PolyFace f) => ReadS (PolyVertex f)
readSVertex (c1:c2:c3:cs) = maybeToList $ do
  f1 <- nameToMaybeFace c1
  f2 <- nameToMaybeFace c2
  f3 <- nameToMaybeFace c3
  v <- facesToMaybeVertex [f1, f2, f3]
  return (v, cs)
readSVertex _ = []
